package ogmios.components;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.filechooser.*;
import java.io.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.Stack;
import java.util.Set;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.jar.*;
import javax.swing.text.*;
import java.util.regex.Pattern;
import ogmios.dialogs.*;
import ogmios.core.*;

/**
 * This class provides a basic interface to portraying a file system through a JTree.<br>
 * This class will undoubtedly be changed greatly in future versions of Ogmios, so
 * it's usage ought to be limited.
 */
public class FileTree extends JTree
{
 private final DefaultTreeModel treeModel;
 private final DefaultMutableTreeNode root;
 public FileTree(String rootName)
 {
  setModel(treeModel=new DefaultTreeModel(root = new DefaultMutableTreeNode(rootName)));

    setCellRenderer(
      new DefaultTreeCellRenderer()
    {
      public Component getTreeCellRendererComponent(JTree tree, Object val,
          boolean selected,
          boolean expanded, boolean leaf,
          int row, boolean hasFocus)
      {
        super.getTreeCellRendererComponent(tree, val, selected, expanded, leaf, row, hasFocus);
        setIcon( leaf ?
                 val instanceof FileNode ?
                 ((FileNode)val).nodeType == FileNode.FOLDER ? getClosedIcon() :
                 getLeafIcon() :
                 getClosedIcon() :
                 expanded ? getOpenIcon() :
                 getClosedIcon()
               );
        return this;
      }
    }
    );

 }
 public FileTree()
 {
  this("Root Folder");
 }

 /**
 * Allows user to add file(s) to the currently selected node.<br>
 * If the node selected is not a folder node, the method continues as though the selected node's parent was selected.<br>
 * If no node is selected, the method continues as though the root was selected.<br>
 * If the 'selected' node is the root, a file dialog appears. Otherwise, the user is restricted to files that exist in the selected node's folder.
 */
 public void addFile()
 {
         TreePath path = getSelectionPath();
        DefaultMutableTreeNode n;
        if (path != null)
        {
          n = (DefaultMutableTreeNode)path.getLastPathComponent();
          if (n instanceof FileNode)
          {
            FileNode jarn = (FileNode)n;

            if (jarn.nodeType == FileNode.FOLDER)
            {
              addNodesPrompt(jarn, false, null);
              return;
            }
            else
           {
             TreeNode parent = jarn.getParent();
             if (parent != null)
             {
               if (parent != root)
               {
                 addNodesPrompt((FileNode)parent, false, null);
                 return;
               }
             }
           }
          }

        }
        //root
        path = new TreePath(root.getPath());
       JFileChooser fileDialog = OgmiosFileChooser.getFilteredFileDialog(null);
        fileDialog.setDialogTitle("Add Files");

        if (fileDialog.showOpenDialog(SwingUtilities.windowForComponent(this)) == JFileChooser.APPROVE_OPTION)
        {

          for (File file: fileDialog.getSelectedFiles())
          {

            treeModel.insertNodeInto(new FileNode(file.getPath(), FileNode.FILE), root, root.getChildCount());

          }
          expandPath(path);
        }

 
 }

 /**
 * Adds new FileNode to parent node
 * @param     parent     The node to add to. Should be a folder node, unless it is the root.
 * @param     child     The name of the new node
 * @param     isFile     Whether the new node is a file
 * @return     The node added to the parent
 */
 public FileNode addNewNodeTo(DefaultMutableTreeNode parent, String child, boolean isFile)
 {
  FileNode newNode;
  treeModel.insertNodeInto(newNode=new FileNode(child, isFile ? FileNode.FILE : FileNode.FOLDER), parent, 0);
  return newNode;
 }
 /**
 * Allows user to add folder(s) to the currently selected node.<br>
 * If the node selected is not a folder node, the method continues as though the selected node's parent was selected.<br>
 * If no node is selected, the method continues as though the root was selected.<br>
 * If the 'selected' node is the root, a folder dialog appears. Otherwise, the user is restricted to folders that exist in the selected node's folder.
 */
 public void addFolder()
 {
       TreePath path = getSelectionPath();
        DefaultMutableTreeNode n;
        if (path != null)
        {
          n = (DefaultMutableTreeNode)path.getLastPathComponent();
          if (n instanceof FileNode)
          {
            FileNode jarn = (FileNode)n;

            if (jarn.nodeType == FileNode.FOLDER)
            {
              addNodesPrompt(jarn, true, null);
              return;
            }
            else
           {
             TreeNode parent = jarn.getParent();
             if (parent != null)
             {
               if (parent != root)
               {
                 addNodesPrompt((FileNode)parent, true, null);
                 return;
               }
             }
           }

          }

        }
        //root
        path = new TreePath(root.getPath());
        JFileChooser folderDialog = OgmiosFileChooser.getFolderDialog();
        folderDialog.setMultiSelectionEnabled(true);
        if (folderDialog.showOpenDialog(SwingUtilities.windowForComponent(this)) == JFileChooser.APPROVE_OPTION)
        {
          for (File folder : folderDialog.getSelectedFiles())
          {
  
            treeModel.insertNodeInto(new FileNode(folder.getPath()), root, root.getChildCount());
          }

          expandPath(path);
        }


 }

 /**
 * Allows user to add file(s) to the currently selected node, as long as they match the user's given list of extensions.<br>
 * If the node selected is not a folder node, the method continues as though the selected node's parent was selected.<br>
 * If no node is selected, the method continues as though the root was selected.<br>
 * If the 'selected' node is the root, a folder dialog appears. Otherwise, the user is restricted to files that exist in the selected node's folder.
 */
 public void addFilesByFilter()
 {
         TreePath path = getSelectionPath();
        DefaultMutableTreeNode n;
        if (path != null)
        {
          n = (DefaultMutableTreeNode)path.getLastPathComponent();
          if (n instanceof FileNode)
          {
            FileNode jarn = (FileNode)n;

            if (jarn.nodeType == FileNode.FOLDER)
            {
              String s;
              if ((s = JOptionPane.showInputDialog(SwingUtilities.windowForComponent(this), "Enter extension filters, separated by a space (Example: .java .class .png)", "Add Files By Filter", JOptionPane.QUESTION_MESSAGE)) != null && s.length() != 0)
              {
                Set<String> extensions = new HashSet<String>();
                for (StringTokenizer tokenizer = new StringTokenizer(s, " "); tokenizer.hasMoreTokens(); )
                {
                  extensions.add(tokenizer.nextToken());
                }
                for (File file : new File(jarn.path + File.separator + jarn.name).listFiles(new MultiOgmiosFileFilter(extensions, "", "User Extensions")))
                {
                  treeModel.insertNodeInto(new FileNode(file.getPath(), FileNode.FILE), jarn, jarn.getChildCount());
                  expandPath(path);
                }
              }
              else
              {
                return;
              }

              return;
            }
            else
            {
              TreeNode parent = jarn.getParent();
              if (parent!=null && parent!=root)
              {
              jarn = (FileNode) parent;
              String s;
              if ((s = JOptionPane.showInputDialog(SwingUtilities.windowForComponent(this), "Enter extension filters, separated by a space (Example: .java .class .png)", "Add Files By Filter", JOptionPane.QUESTION_MESSAGE)) != null && s.length() != 0)
              {
                Set<String> extensions = new HashSet<String>();
                for (StringTokenizer tokenizer = new StringTokenizer(s, " "); tokenizer.hasMoreTokens(); )
                {
                  extensions.add(tokenizer.nextToken());
                }
                for (File file : new File(jarn.path + File.separator + jarn.name).listFiles(new MultiOgmiosFileFilter(extensions, "", "User Extensions")))
                {
                  treeModel.insertNodeInto(new FileNode(file.getPath(), FileNode.FILE), jarn, jarn.getChildCount());
                  expandPath(path);
                }    
               return;             
              }
             }
            }
          }

        }
        //root
        path = new TreePath(root.getPath());
       JFileChooser folderDialog = OgmiosFileChooser.getFolderDialog();
        folderDialog.setMultiSelectionEnabled(true);
        if (folderDialog.showOpenDialog(SwingUtilities.windowForComponent(this)) == JFileChooser.APPROVE_OPTION)
        {
          String s;
          if ((s = JOptionPane.showInputDialog(SwingUtilities.windowForComponent(this), "Enter extension filters, separated by a space (Example: .java .class .png)", "Add Files By Filter", JOptionPane.QUESTION_MESSAGE)) != null && s.length() != 0)
          {
            Set<String> extensions = new HashSet<String>();
            for (StringTokenizer tokenizer = new StringTokenizer(s, " "); tokenizer.hasMoreTokens(); )
            {
              extensions.add(tokenizer.nextToken());
            }

            for (File folder: folderDialog.getSelectedFiles())
            {
              for (File file : folder.listFiles(new MultiOgmiosFileFilter(extensions, "", "User Extensions")))
                treeModel.insertNodeInto(new FileNode(file.getPath(), FileNode.FILE), root, root.getChildCount());

            }
            expandPath(path);
          }
         
        }

 }

 /**
 * Allows user to remove all selected file(s)/folder(s).<br>
 * If a folder is removed, all of its subfiles/subfolders are also removed, even if they were not selected.<br>
 * The root cannot be removed; however, attempting to remove the root will remove all of the root's subfiles/subfolders.
 */
 public void removeSelected()
 {
        if (getSelectionCount() != 0)
        {

          TreePath path = new TreePath(root.getPath());
          if (getSelectionModel().isPathSelected(path))
          {
            root.removeAllChildren();
            treeModel.reload();
          }
          else
            while (getSelectionCount() != 0)
            {
              treeModel.removeNodeFromParent((MutableTreeNode)getSelectionPath().getLastPathComponent());
            }

   }
 }

  /**
  * Shows the prompt to users for adding nodes to the file tree
  * @param     node     The node to add to
  * @param     directory     Whether the new node is supposed to be a directory
  * @param     extensions     An optional set of extensions to match potential files with
  */
  public void addNodesPrompt(final FileNode node, boolean directory, Set<String> extensions)
  {
    DefaultListModel nodeList = new DefaultListModel();
    if (directory)
    {
      for (File f : new File(node.path + File.separator + node.name).listFiles())
        if (f.isDirectory() && f.isHidden() == false)
          nodeList.addElement(new FileNode(f.getPath()));
    }
    else
    {
      if (extensions != null)
      {
        for (File f : new File(node.path + File.separator + node.name).listFiles(new MultiOgmiosFileFilter(extensions, "", "User Extensions")))
          if (f.isHidden() == false)
            nodeList.addElement(new FileNode(f.getPath(), FileNode.FILE));
      }
      else
      {
        for (File f : new File(node.path + File.separator + node.name).listFiles())
          if (f.isFile() && f.isHidden() == false)
            nodeList.addElement(new FileNode(f.getPath(), FileNode.FILE));
      }
    }
    if (nodeList.size() == 0)
      return;
    final JDialog nodeLoader = new JDialog();
    nodeLoader.setModal(true);
    nodeLoader.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    final JList list  = new JList(nodeList);
    //list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    list.addKeyListener(
      new KeyAdapter()
    {
      public void keyPressed(KeyEvent e)
      {
        if (list.getSelectedValue() != null && e.getKeyCode() == KeyEvent.VK_ENTER)
          ((JButton)nodeLoader.getContentPane().getComponent(2)).doClick();
      }
    }
    );
    nodeLoader.setTitle("Node Loader");

    Container contents = nodeLoader.getContentPane();
    JButton load = new JButton("Add");
    load.addActionListener(
      new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        for (Object item : list.getSelectedValues())
          treeModel.insertNodeInto( (FileNode)item, node, node.getChildCount());
        expandPath(new TreePath(node.getPath()));
        nodeLoader.setVisible(false);

      }
    }
    );
    contents.add(new JLabel("Looking inside " + node.name, JLabel.CENTER), BorderLayout.NORTH);
    contents.add(new JScrollPane(list));
    contents.add(load, BorderLayout.SOUTH);
    nodeLoader.pack();
    nodeLoader.setLocationRelativeTo(SwingUtilities.windowForComponent(this));
    nodeLoader.setVisible(true);
  }

  /**
  * A representation of files or folders that the FileTree uses.
  */
  public static class FileNode extends DefaultMutableTreeNode
  {
    public static final int FILE = 0;
    public static final int FOLDER = 1;
    /**
    * Either FileNode.FILE or FileNode.FOLDER. In the future, this may be replaced with a boolean.
    */
    public final int nodeType;
    public String name;
    public String path;
    public FileNode(String name)
    {
      setName(name);
      nodeType = FileNode.FOLDER;
    }

    public boolean equals(FileNode node)
    {
      return node.nodeType == nodeType && node.name.equals(name) && node.path.equals(path);
    }

    /**
    * Sets name and path based off the full file name passed.
    */
    public void setName(String name)
    {
      int i = name.lastIndexOf(File.separator);
      if (i == -1)
      {
        this.name = name;
        path = "";
      }
      else
      {
        this.name = name.substring(i + 1);
        this.path = name.substring(0, i);
      }
      setUserObject(this.name);
    }
    public FileNode(String name, int nodeType)
    {
      setName(name);
      this.nodeType = nodeType;
    }
  }
 
}
